多条命令的"原子 + 条件逻辑"组合(如"取锁-校验-删除")是 Redis 编程的高频需求。Lua 脚本让一段逻辑在 Redis 服务端原子执行;**Redis Functions(7.0+)**则是脚本的工程化升级:库化管理、版本化、复用与权限控制。本章从 EVAL 原理到 Functions 实践完整展开。
redis.call() / redis.pcall() 调用 Redis 命令(pcall 捕获错误不中断脚本);lua-time-limit(默认 5s)后,Redis 对其他命令统一回复 BUSY 错误,仅接受 SCRIPT KILL(脚本尚未执行过写命令时)或 SHUTDOWN NOSAVE。> eval "return redis.call('set', KEYS[1], ARGV[1])" 1 mykey hello
OK
> eval "return {KEYS[1], ARGV[1]}" 1 k v
1) "k"
2) "v"规则:
KEYS[i] 传 key(必须走 KEYS 传参,不能拼进脚本字符串——集群路由依赖它);ARGV[i] 传参数;-- 加锁:SET NX PX 的脚本等价(演示条件逻辑)
if redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) then
return 1
end
return 0-- 释放:值与 token 匹配才删除(安全性核心)
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end-- 限流:固定窗口原子版
local current = redis.call("incr", KEYS[1])
if current == 1 then
redis.call("expire", KEYS[1], ARGV[1])
end
if current > tonumber(ARGV[2]) then
return 0
end
return 1每次 EVAL 都要传输脚本全文(大脚本浪费带宽)。Redis 对脚本内容做 SHA1 摘要并缓存(SCRIPT LOAD 显式缓存),之后用 EVALSHA <sha1> 执行:
> script load "return redis.call('get', KEYS[1])"
"e0e1f9fabfc9d4800c877a703b823ac0578ffabe"
> evalsha e0e1f9fabfc9d4800c877a703b823ac0578ffabe 1 mykey
"hello"SCRIPT FLUSH、重启会清空脚本缓存——正确姿势是先试 EVALSHA,收到 NOSCRIPT 错误后回退 EVAL(EVAL 会顺带把脚本缓存住);| 问题 | EVAL 脚本 | Redis Functions |
|---|---|---|
| 管理与复用 | 脚本散落客户端 | 库(Library)集中管理,服务端持有 |
| 版本化 | 无 | 库可覆盖(REPLACE),版本演进清晰 |
| 权限 | 无(脚本可执行任意命令) | 按用户 ACL 校验库中命令 |
| 缓存 | 需管理 SHA1 | 免管理(FCALL 按库名+函数名调用) |
| 调试 | 无 | 库内函数可独立调试 |
-- mylib.lua
#!lua name=mylib
redis.register_function("incr_with_cap", function(keys, args)
local current = redis.call("incr", keys[1])
local cap = tonumber(args[1])
if current > cap then
redis.call("decr", keys[1])
return redis.error_reply("over capacity")
end
return current
end)# 加载库(REPLACE 覆盖同名库)
$ redis-cli -x FUNCTION LOAD REPLACE < mylib.lua
mylib
# 调用
> fcall incr_with_cap 1 counter 5
(integer) 1
> fcall incr_with_cap 1 counter 5
(integer) 2
# ……第 6 次调用:incr 后为 6 > 5 → 回退计数并报错
> fcall incr_with_cap 1 counter 5
(error) ERR over capacity> function list # 列出已加载库
> function dump # 导出库(备份/迁移)
> function restore <payload> # 恢复库
> function stats # 运行统计
> function flush # 清空库(慎用)7.x 建议:新脚本一律用 Functions 编写与加载;存量 EVAL 脚本按页迁移(Functions 与 EVAL 可共存)。
redis.error_reply():返回语义化错误而非让脚本崩溃;EVAL_RO/FCALL_RO(7.0+):只读副本上可执行,提升扩展性;INFO commandstats 的 cmdstat_eval 查看脚本耗时,SLOWLOG 抓慢脚本。| 坑 | 说明 |
|---|---|
脚本内 random 等非确定性命令 | 7.0 前按 whole-script 复制时被禁止;7.0 起默认按效果复制(effect replication)已放开,但仍建议少用(主从重放与排障更复杂) |
脚本内 KEYS[1] 拼字符串访问其他 key | 集群下 CROSSSLOT,且不经过 key 声明 |
| 大循环 | 阻塞全库,先小数据量压测 |
| 依赖外部状态 | 脚本应纯函数(入参 + Redis 数据),不依赖全局变量 |
| 需求 | 选型 |
|---|---|
| 无条件批量原子执行 | MULTI/EXEC |
| 条件逻辑 + 原子 | Lua/Functions |
| 读-改-写并发控制 | WATCH 重试 或 Lua |
| 服务端可复用逻辑 | Functions(7.0 首选) |
下一章讲解命令行工具:redis-cli 的高级用法与调试技巧。